/**
 * Copyright 2018 orivil.com. All rights reserved.
 * Use of this source code is governed by a MIT-style
 * license that can be found at https://mit-license.org.
 */

package n_spider

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"regexp"
	"strconv"
	"sync"
	"time"
)

type taskStatus string

const (
	TaskReady       taskStatus = "等待下载"
	TaskDownloading            = "正在下载"
	TaskEnd                    = "下载已结束"
)

type ErrWrongStatus struct {
	status taskStatus
}

func (e *ErrWrongStatus) Error() string {
	return fmt.Sprintf("当前状态: %s", e.status)
}

const maxTaskRange = 2000

func checkTask(t *Task) error {
	if len(t.IDs) == 0 && t.EndID > 0 {
		m := t.EndID - t.StartID
		if m <= 0 || m > maxTaskRange {
			return fmt.Errorf("必须指定下载或下载范围在%d以内", maxTaskRange)
		}
	}
	return nil
}

type TaskMux struct {
	platformID int
	status     taskStatus // 下载状态
	storage    Storage    // 持久存储器
	downloader Downloader // 下载器
	logger     *logger    // 状态监听, 实时返回服务器下载状态, 缓存错误信息
	task       *Task      // 任务数据
	auth       *Auth
	locker     sync.Mutex
}

func NewTaskMux(ptID int, storage Storage, downloader Downloader) *TaskMux {
	return &TaskMux{
		platformID: ptID,
		storage:    storage,
		downloader: downloader,
		logger:     newLogger(),
	}
}

// 设置权限
func (ns *TaskMux) SetAuth(wait time.Duration, headers []*Header) {
	ns.locker.Lock()
	defer ns.locker.Unlock()
	ns.auth = &Auth{Wait: wait, Headers: headers}
}

// 添加实时日志接收器
func (ns *TaskMux) AddLogListener(writer io.Writer) {
	ns.logger.add(writer)
}

// 获得图书的错误日志
func (ns *TaskMux) GetLogs() (warns, errs, successes logs) {
	ns.locker.Lock()
	defer ns.locker.Unlock()
	return ns.logger.getLogs()
}

// 设置任务信息, 如果已设置则会返回错误, 可使用 GetTask() 方法获得任务的最新信息, 以创建新的任务继续下载
func (ns *TaskMux) SetTask(task *Task) error {
	err := checkTask(task)
	if err != nil {
		return err
	}
	ns.locker.Lock()
	defer ns.locker.Unlock()
	if ns.task != nil {
		return ErrTaskExist
	} else {
		ns.task = task
		return nil
	}
}

// 获得任务最新信息
func (ns *TaskMux) GetTask() *Task {
	ns.locker.Lock()
	defer ns.locker.Unlock()
	return ns.task
}

// 任务状态
func (ns *TaskMux) Status() taskStatus {
	ns.locker.Lock()
	defer ns.locker.Unlock()
	return ns.status
}

func (ns *TaskMux) mustBeStatus(ss ...taskStatus) error {
	ns.locker.Lock()
	defer ns.locker.Unlock()
	for _, s := range ss {
		if ns.status == s {
			return nil
		}
	}
	return &ErrWrongStatus{status: ns.status}
}

// 获得任务图书ID
func (ns *TaskMux) PtBookIDs() []int {
	ns.locker.Lock()
	defer ns.locker.Unlock()
	if len(ns.task.IDs) > 0 {
		return ns.task.IDs
	} else {
		ids := make([]int, ns.task.EndID-ns.task.StartID+1)
		key := 0
		for i := ns.task.StartID; i <= ns.task.EndID; i++ {
			ids[key] = i
			key++
		}
		return ids
	}
}

// 开始任务
func (ns *TaskMux) Start() error {
	err := ns.mustBeStatus(TaskReady)
	if err != nil {
		return err
	}

	ptBookIDs := ns.PtBookIDs()

	ns.locker.Lock()
	defer ns.locker.Unlock()
	ns.status = TaskDownloading

	go func() {
		defer func() {
			err := recover()
			if err != nil {
				ns.logger.err.Println(err)
			}
		}()
		for _, ptBookID := range ptBookIDs {
			// 初始化 log 打印器
			ns.logger.init(ptBookID)
			var (
				book *Book
				err  error
			)
			if !ns.task.Cover {
				if ns.storage.IsFinishedBook(ns.platformID, ptBookID) {
					ns.logger.success.Println("已完结")
					continue
				} else {
					book = ns.storage.GetUnfinishedBook(ns.platformID, ptBookID)
					ns.logger.info.Println("开始更新图书...")
				}
			}
			if book == nil {
				ns.logger.info.Println("开始下载新书...")
				// 下载图书信息
				book, err = ns.downloader.DownloadBook(ns.platformID, ptBookID, ns.auth.Header())
				if err != nil {
					ns.logger.err.Println(err)
				} else {
					total := ns.storage.CountBooks(book.Title, book.Author)
					if (ns.task.Cover && total <= 1) || (!ns.task.Cover && total == 0) {
						// 创建或更新图书
						err := ns.storage.SaveUnfinishedBook(ns.platformID, ptBookID, book)
						if err != nil {
							ns.logger.err.Println(err)
						}
					} else {
						ns.logger.err.Printf("跳过同名书籍: 作者-%s 图书-《%s》", book.Author, book.Title)
						continue
					}
				}
			}

			var downChapter int
			if book != nil {
				// 初始下载章节
				if ns.task.Cover {
					downChapter = book.FirstChapter
				} else {
					chapter := ns.storage.GetLastChapter(ns.platformID, ptBookID)
					if chapter != nil {
						_, nextChapter, _ := ns.downloader.DownloadChapter(ns.platformID, ptBookID, chapter.PtChapter, ns.auth.Header())
						if nextChapter > 0 {
							downChapter = nextChapter
						}
					} else {
						downChapter = book.FirstChapter
					}
				}

				ns.logger.info.Println("开始下载章节")
				var words int
				for {
					// 检测是否有停止指令
					if func() (stop bool) {
						ns.locker.Lock()
						defer ns.locker.Unlock()
						if ns.status == TaskEnd {
							if words > 0 {
								ns.storage.AddBookWords(ns.platformID, ptBookID, words)
								ns.logger.success.Printf("更新%d字", words)
								ns.logger.info.Println("停止下载")
							}
							return true
						} else {
							return false
						}
					}() {
						return
					}

					if downChapter > 0 {
						chapter, nextChapter, _ := ns.downloader.DownloadChapter(ns.platformID, ptBookID, downChapter, ns.auth.Header())
						downChapter = nextChapter
						if ws := len([]rune(chapter.Content)); ws > 0 {
							// 创建或更新章节
							err = ns.storage.SaveChapter(ns.platformID, ptBookID, chapter.PtChapter, chapter)
							if err != nil {
								ns.logger.err.Println(err)
							} else {
								words += ws
								ns.logger.info.Println(chapter.PtChapter, chapter.Title)
							}
						}
					} else {
						break
					}
				}
				if words > 0 {
					ns.storage.AddBookWords(ns.platformID, ptBookID, words)
					ns.logger.success.Printf("更新%d字", words)
				} else {
					ns.logger.warn.Println("未下载新章节")
				}
				if ns.downloader.CheckBookFinished(ns.platformID, ptBookID, ns.auth.Header()) {
					ns.storage.SetBookFinished(ns.platformID, ptBookID)
				}
			}
		}
		ns.locker.Lock()
		ns.status = TaskEnd
		ns.locker.Unlock()
	}()
	return nil
}

// 停止任务
func (ns *TaskMux) Stop() error {
	err := ns.mustBeStatus(TaskDownloading)
	if err != nil {
		return err
	}
	ns.locker.Lock()
	defer ns.locker.Unlock()
	ns.status = TaskEnd
	return nil
}

type logger struct {
	writers   []io.Writer
	warns     logs // 警告缓存, 保存当前任务的所有警告信息
	errs      logs // 错误缓存, 保存当前任务的所有错误信息
	successes logs
	info      *log.Logger
	warn      *log.Logger
	err       *log.Logger
	success   *log.Logger
	locker    sync.Mutex
}

type ptBookID int

// 图书错误日志
type logs map[ptBookID][]string

func (ls logs) copy() logs {
	dst := make(logs, len(ls))
	for key, msgs := range ls {
		var vs []string
		copy(vs, msgs)
		dst[key] = vs
	}
	return dst
}

var (
	infoPrefix    = []byte("[info]")
	warnPrefix    = []byte("[warning]")
	errPrefix     = []byte("[error]")
	successPrefix = []byte("[success]")
)

func newLogger() *logger {
	return &logger{
		warns:     make(logs, 10),
		errs:      make(logs, 10),
		successes: make(logs, 10),
	}
}

func (l *logger) init(ptBookID int) {
	l.locker.Lock()
	defer l.locker.Unlock()
	l.info = log.New(l, fmt.Sprintf(string(infoPrefix)+"[%d]", ptBookID), log.LstdFlags)
	l.warn = log.New(l, fmt.Sprintf(string(warnPrefix)+"[%d]", ptBookID), log.Llongfile|log.LstdFlags)
	l.err = log.New(l, fmt.Sprintf(string(errPrefix)+"[%d]", ptBookID), log.Llongfile|log.LstdFlags)
	l.success = log.New(l, fmt.Sprintf(string(successPrefix)+"[%d]", ptBookID), log.LstdFlags)
}

func (l *logger) getLogs() (warns, errs, successes logs) {
	l.locker.Lock()
	defer l.locker.Unlock()
	warns = l.warns.copy()
	errs = l.errs.copy()
	successes = l.successes.copy()
	return
}

func (l *logger) add(writer io.Writer) {
	l.locker.Lock()
	defer l.locker.Unlock()
	l.writers = append(l.writers, writer)
}

func (l *logger) del(key int) {
	lk := len(l.writers) - 1
	l.writers[key] = l.writers[lk]
	l.writers = l.writers[:lk]
}

var bookIDPattern = regexp.MustCompile(`\[\d+\]`)

// [error][4212] log message => 4212
func getBookID(str []byte) ptBookID {
	str = bookIDPattern.Find(str)
	if len(str) > 3 {
		str = str[1 : len(str)-1]
		i, _ := strconv.Atoi(string(str))
		return ptBookID(i)
	} else {
		return 0
	}
}

func (l *logger) Write(p []byte) (n int, err error) {
	l.locker.Lock()
	defer l.locker.Unlock()
	// 保存图书的错误信息
	if bytes.HasPrefix(p, []byte(warnPrefix)) {
		bookID := getBookID(p)
		l.warns[bookID] = append(l.warns[bookID], string(p))
	} else if bytes.Contains(p, []byte(errPrefix)) {
		bookID := getBookID(p)
		l.errs[bookID] = append(l.errs[bookID], string(p))
	} else if bytes.Contains(p, []byte(successPrefix)) {
		bookID := getBookID(p)
		l.successes[bookID] = append(l.successes[bookID], string(p))
	}
	// 实时打印所有信息
	for key, writer := range l.writers {
		n, err = writer.Write(p)
		if err != nil { // 出错则移除 writer
			l.del(key)
		}
	}
	return
}
